/*
 * Copyright 2020 by OLTPBenchmark Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.oltpbenchmark.distributions;

/**
 * A generator of a zipfian distribution. It produces a sequence of items, such that some items are
 * more popular than others, according to a zipfian distribution. When you construct an instance of
 * this class, you specify the number of items in the set to draw from, either by specifying an
 * itemcount (so that the sequence is of items from 0 to itemcount-1) or by specifying a min and a
 * max (so that the sequence is of items from min to max inclusive). After you construct the
 * instance, you can change the number of items by calling nextInt(itemcount) or
 * nextLong(itemcount).
 *
 * <p>Unlike @ZipfianGenerator, this class scatters the "popular" items across the itemspace. Use
 * this, instead of @ZipfianGenerator, if you don't want the head of the distribution (the popular
 * items) clustered together.
 */
public class ScrambledZipfianGenerator extends IntegerGenerator {
  public static final double ZETAN = 26.46902820178302;
  public static final double USED_ZIPFIAN_CONSTANT = 0.99;
  public static final long ITEM_COUNT = 10000000000L;

  ZipfianGenerator gen;
  long _min, _max, _itemcount;

  /**
   * Create a zipfian generator for the specified number of items.
   *
   * @param _items The number of items in the distribution.
   */
  public ScrambledZipfianGenerator(long _items) {
    this(0, _items - 1);
  }

  /**
   * Create a zipfian generator for items between min and max.
   *
   * @param _min The smallest integer to generate in the sequence.
   * @param _max The largest integer to generate in the sequence.
   */
  public ScrambledZipfianGenerator(long _min, long _max) {
    this(_min, _max, ZipfianGenerator.ZIPFIAN_CONSTANT);
  }

  /**
   * Create a zipfian generator for items between min and max (inclusive) for the specified zipfian
   * constant. If you use a zipfian constant other than 0.99, this will take a long time to complete
   * because we need to recompute zeta.
   *
   * @param min The smallest integer to generate in the sequence.
   * @param max The largest integer to generate in the sequence.
   * @param _zipfianconstant The zipfian constant to use.
   */
  public ScrambledZipfianGenerator(long min, long max, double _zipfianconstant) {
    _min = min;
    _max = max;
    _itemcount = _max - _min + 1;
    if (_zipfianconstant == USED_ZIPFIAN_CONSTANT) {
      gen = new ZipfianGenerator(Utils.random(), 0, ITEM_COUNT, _zipfianconstant, ZETAN);
    } else {
      gen = new ZipfianGenerator(Utils.random(), 0, ITEM_COUNT, _zipfianconstant);
    }
  }

  /** Return the next int in the sequence. */
  @Override
  public int nextInt() {
    return (int) nextLong();
  }

  /** Return the next long in the sequence. */
  public long nextLong() {
    long ret = gen.nextLong();
    ret = _min + Utils.FNVhash64(ret) % _itemcount;
    setLastInt((int) ret);
    return ret;
  }

  /**
   * since the values are scrambled (hopefully uniformly), the mean is simply the middle of the
   * range.
   */
  @Override
  public double mean() {
    return ((double) (_min + _max)) / 2.0;
  }
}
